/****************************************************************************
 * Copyright 2010 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.greatq;

import com.gotobject.filter.internal.ToStrings;
import com.gotobject.greatq.internal.Chronometer;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.gotobject.filter.internal.ToStrings.str;
import static com.gotobject.filter.internal.Pair.of;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
class QuerySubmitterImpl implements QuerySubmitter {
    private final List<Throwable>                   errors;
    private final List<QuerySubmissionBuilderImpl>  queries;
    private final Connection                        connection;
    private final Chronometer                       stopWatch;

    private QueryServiceImpl serviceImpl;

    public QuerySubmitterImpl(Connection connection) {
        this.connection = connection;
        errors          = new ArrayList<Throwable>();
        queries         = new ArrayList<QuerySubmissionBuilderImpl>();
        stopWatch       = new Chronometer();
    }

    @Override
    public void addError(Throwable cause) {
        if(!errors.contains(cause)) errors.add(cause);
    }

    @Override
    @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
    public void addError(String message) {
        addError(makeException(message));
    }

    @Override
    public void install(Module module) {
        module.configure(this);
    }

    @Override
    public QuerySubmissionBuilder submitBatch(String... sqls) {
        final QuerySubmissionBuilderImpl mquery = new QuerySubmissionBuilderImpl(this, sqls);
        queries.add(mquery);
        return mquery;
    }

    public QueryService createQueryService() throws QueryingException {
        stopWatch.resetAndLog("service:creation");
        final Map<String, Query> answers = new HashMap<String, Query>();
        serviceImpl = new QueryServiceImpl(answers, connection);
        createQueries();
        stopWatch.resetAndLog("query objects:creation");
        // lazy throwing of errors during the building of the serviceImpl
        throwFailedQueryingExceptions();
        return serviceImpl;
    }

    private void createQueries() {
        for(QuerySubmissionBuilderImpl each : queries){
            if(!each.hasKey()){
                addError("error: query does not have a key");
            } else {
                createQueryPerBuilder(each);
            }
        }
    }

    private void createQueryPerBuilder(QuerySubmissionBuilderImpl builder){
        if(builder.hasKey()){
            final String key = builder.getKey();
            try {
                putQuery(
                        builder.inBatchMode()
                                ? new BatchedQueryImpl(serviceImpl, key, builder.getBatchedSqls())
                                : new SingleQueryImpl(serviceImpl, key, builder.getSql())
                );
            } catch (SQLException e) {
                addError(e);
            }
        } else {
            addError("error:missing key.");
        }
    }

    void putQuery(Query qry){
        final String                    key      = qry.getTag();
        final Map<String, Query>    mappings = serviceImpl.getInternalRequests();
        if(mappings.containsKey(key)){
            addError("error:query already set.");
        } else {
            mappings.put(key, qry);
        }
        
    }

    @Override
    public QuerySubmissionBuilder submit(String sql) {
        final QuerySubmissionBuilderImpl kquery = new QuerySubmissionBuilderImpl(this, sql);
        queries.add(kquery);
        return kquery;
    }

    private static Exception makeException(String t) {
        try {
            final Constructor<Exception> c = Exception.class.getDeclaredConstructor(String.class);
            return c.newInstance(String.format("Internal error when checking prerequisites. %s", t));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void throwFailedQueryingExceptions(){
        if(errors.isEmpty()) return;
        throw new QueryingException(errors);
    }

    @Override
    public String toString() {
        return ToStrings.toString(
                this,
                of("connection", str(connection)),
                of("errors", str(errors))
        );
    }
}
